#!/usr/bin/python
#
# Copyright (C) 2019 Huawei
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import annotations

###############################################################################
# Documentation
###############################################################################

DOCUMENTATION = r"""
module: hwc_ecs_instance
description:
  - Instance management.
short_description: Creates a resource of Ecs/Instance in Huawei Cloud
version_added: '0.2.0'
author: Huawei Inc. (@huaweicloud)
requirements:
  - keystoneauth1 >= 3.6.0
attributes:
  check_mode:
    support: full
  diff_mode:
    support: none
options:
  state:
    description:
      - Whether the given object should exist in Huawei Cloud.
    type: str
    choices: ['present', 'absent']
    default: 'present'
  timeouts:
    description:
      - The timeouts for each operations.
    type: dict
    default: {}
    suboptions:
      create:
        description:
          - The timeouts for create operation.
        type: str
        default: '30m'
      update:
        description:
          - The timeouts for update operation.
        type: str
        default: '30m'
      delete:
        description:
          - The timeouts for delete operation.
        type: str
        default: '30m'
  availability_zone:
    description:
      - Specifies the name of the AZ where the ECS is located.
    type: str
    required: true
  flavor_name:
    description:
      - Specifies the name of the system flavor.
    type: str
    required: true
  image_id:
    description:
      - Specifies the ID of the system image.
    type: str
    required: true
  name:
    description:
      - Specifies the ECS name. Value requirements consists of 1 to 64 characters, including letters, digits, underscores
        (V(_)), hyphens (V(-)), periods (V(.)).
    type: str
    required: true
  nics:
    description:
      - Specifies the NIC information of the ECS. Constraints the network of the NIC must belong to the VPC specified by vpc_id.
        A maximum of 12 NICs can be attached to an ECS.
    type: list
    elements: dict
    required: true
    suboptions:
      ip_address:
        description:
          - Specifies the IP address of the NIC. The value is an IPv4 address. Its value must be an unused IP address in the
            network segment of the subnet.
        type: str
        required: true
      subnet_id:
        description:
          - Specifies the ID of subnet.
        type: str
        required: true
  root_volume:
    description:
      - Specifies the configuration of the ECS's system disks.
    type: dict
    required: true
    suboptions:
      volume_type:
        description:
          - Specifies the ECS system disk type.
          - SATA is common I/O disk type.
          - SAS is high I/O disk type.
          - SSD is ultra-high I/O disk type.
          - Co-p1 is high I/O (performance-optimized I) disk type.
          - Uh-l1 is ultra-high I/O (latency-optimized) disk type.
          - NOTE is For HANA, HL1, and HL2 ECSs, use co-p1 and uh-l1 disks. For other ECSs, do not use co-p1 or uh-l1 disks.
        type: str
        required: true
      size:
        description:
          - Specifies the system disk size, in GB. The value range is 1 to 1024. The system disk size must be greater than
            or equal to the minimum system disk size supported by the image (min_disk attribute of the image). If this parameter
            is not specified or is set to 0, the default system disk size is the minimum value of the system disk in the image
            (min_disk attribute of the image).
        type: int
      snapshot_id:
        description:
          - Specifies the snapshot ID or ID of the original data disk contained in the full-ECS image.
        type: str
  vpc_id:
    description:
      - Specifies the ID of the VPC to which the ECS belongs.
    type: str
    required: true
  admin_pass:
    description:
      - Specifies the initial login password of the administrator account for logging in to an ECS using password authentication.
        The Linux administrator is root, and the Windows administrator is Administrator. Password complexity requirements,
        consists of 8 to 26 characters. The password must contain at least three of the following character types 'uppercase
        letters, lowercase letters, digits, and special characters (V(!@$%^-_=+[{}]:,./?))'. The password cannot contain the
        username or the username in reverse. The Windows ECS password cannot contain the username, the username in reverse,
        or more than two consecutive characters in the username.
    type: str
  data_volumes:
    description:
      - Specifies the data disks of ECS instance.
    type: list
    elements: dict
    suboptions:
      volume_id:
        description:
          - Specifies the disk ID.
        type: str
        required: true
      device:
        description:
          - Specifies the disk device name.
        type: str
  description:
    description:
      - Specifies the description of an ECS, which is a null string by default. Can contain a maximum of 85 characters. Cannot
        contain special characters, such as V(<) and V(>).
    type: str
  eip_id:
    description:
      - Specifies the ID of the elastic IP address assigned to the ECS. Only elastic IP addresses in the DOWN state can be
        assigned.
    type: str
  enable_auto_recovery:
    description:
      - Specifies whether automatic recovery is enabled on the ECS.
    type: bool
  enterprise_project_id:
    description:
      - Specifies the ID of the enterprise project to which the ECS belongs.
    type: str
  security_groups:
    description:
      - Specifies the security groups of the ECS. If this parameter is left blank, the default security group is bound to
        the ECS by default.
    type: list
    elements: str
  server_metadata:
    description:
      - Specifies the metadata of ECS to be created.
    type: dict
  server_tags:
    description:
      - Specifies the tags of an ECS. When you create ECSs, one ECS supports up to 10 tags.
    type: dict
  ssh_key_name:
    description:
      - Specifies the name of the SSH key used for logging in to the ECS.
    type: str
  user_data:
    description:
      - Specifies the user data to be injected during the ECS creation process. Text, text files, and gzip files can be injected.
        The content to be injected must be encoded with base64. The maximum size of the content to be injected (before encoding)
        is 32 KB. For Linux ECSs, this parameter does not take effect when adminPass is used.
    type: str
extends_documentation_fragment:
  - community.general.hwc
  - community.general.attributes
"""

EXAMPLES = r"""
# create an ecs instance
- name: Create a vpc
  hwc_network_vpc:
    cidr: "192.168.100.0/24"
    name: "ansible_network_vpc_test"
  register: vpc
- name: Create a subnet
  hwc_vpc_subnet:
    gateway_ip: "192.168.100.32"
    name: "ansible_network_subnet_test"
    dhcp_enable: true
    vpc_id: "{{ vpc.id }}"
    cidr: "192.168.100.0/26"
  register: subnet
- name: Create a eip
  hwc_vpc_eip:
    dedicated_bandwidth:
      charge_mode: "traffic"
      name: "ansible_test_dedicated_bandwidth"
      size: 1
    type: "5_bgp"
  register: eip
- name: Create a disk
  hwc_evs_disk:
    availability_zone: "cn-north-1a"
    name: "ansible_evs_disk_test"
    volume_type: "SATA"
    size: 10
  register: disk
- name: Create an instance
  community.general.hwc_ecs_instance:
    data_volumes:
      - volume_id: "{{ disk.id }}"
    enable_auto_recovery: false
    eip_id: "{{ eip.id }}"
    name: "ansible_ecs_instance_test"
    availability_zone: "cn-north-1a"
    nics:
      - subnet_id: "{{ subnet.id }}"
        ip_address: "192.168.100.33"
      - subnet_id: "{{ subnet.id }}"
        ip_address: "192.168.100.34"
    server_tags:
      my_server: "my_server"
    image_id: "8da46d6d-6079-4e31-ad6d-a7167efff892"
    flavor_name: "s3.small.1"
    vpc_id: "{{ vpc.id }}"
    root_volume:
      volume_type: "SAS"
"""

RETURN = r"""
availability_zone:
  description:
    - Specifies the name of the AZ where the ECS is located.
  type: str
  returned: success
flavor_name:
  description:
    - Specifies the name of the system flavor.
  type: str
  returned: success
image_id:
  description:
    - Specifies the ID of the system image.
  type: str
  returned: success
name:
  description:
    - Specifies the ECS name. Value requirements "Consists of 1 to 64 characters, including letters, digits, underscores (V(_)),
      hyphens (V(-)), periods (V(.)).".
  type: str
  returned: success
nics:
  description:
    - Specifies the NIC information of the ECS. The network of the NIC must belong to the VPC specified by vpc_id. A maximum
      of 12 NICs can be attached to an ECS.
  type: list
  returned: success
  contains:
    ip_address:
      description:
        - Specifies the IP address of the NIC. The value is an IPv4 address. Its value must be an unused IP address in the
          network segment of the subnet.
      type: str
      returned: success
    subnet_id:
      description:
        - Specifies the ID of subnet.
      type: str
      returned: success
    port_id:
      description:
        - Specifies the port ID corresponding to the IP address.
      type: str
      returned: success
root_volume:
  description:
    - Specifies the configuration of the ECS's system disks.
  type: dict
  returned: success
  contains:
    volume_type:
      description:
        - Specifies the ECS system disk type.
        - SATA is common I/O disk type.
        - SAS is high I/O disk type.
        - SSD is ultra-high I/O disk type.
        - Co-p1 is high I/O (performance-optimized I) disk type.
        - Uh-l1 is ultra-high I/O (latency-optimized) disk type.
        - NOTE is For HANA, HL1, and HL2 ECSs, use co-p1 and uh-l1 disks. For other ECSs, do not use co-p1 or uh-l1 disks.
      type: str
      returned: success
    size:
      description:
        - Specifies the system disk size, in GB. The value range is 1 to 1024. The system disk size must be greater than or
          equal to the minimum system disk size supported by the image (min_disk attribute of the image). If this parameter
          is not specified or is set to 0, the default system disk size is the minimum value of the system disk in the image
          (min_disk attribute of the image).
      type: int
      returned: success
    snapshot_id:
      description:
        - Specifies the snapshot ID or ID of the original data disk contained in the full-ECS image.
      type: str
      returned: success
    device:
      description:
        - Specifies the disk device name.
      type: str
      returned: success
    volume_id:
      description:
        - Specifies the disk ID.
      type: str
      returned: success
vpc_id:
  description:
    - Specifies the ID of the VPC to which the ECS belongs.
  type: str
  returned: success
admin_pass:
  description:
    - Specifies the initial login password of the administrator account for logging in to an ECS using password authentication.
      The Linux administrator is root, and the Windows administrator is Administrator. Password complexity requirements consists
      of 8 to 26 characters. The password must contain at least three of the following character types "uppercase letters,
      lowercase letters, digits, and special characters (!@$%^-_=+[{}]:,./?)". The password cannot contain the username or
      the username in reverse. The Windows ECS password cannot contain the username, the username in reverse, or more than
      two consecutive characters in the username.
  type: str
  returned: success
data_volumes:
  description:
    - Specifies the data disks of ECS instance.
  type: list
  returned: success
  contains:
    volume_id:
      description:
        - Specifies the disk ID.
      type: str
      returned: success
    device:
      description:
        - Specifies the disk device name.
      type: str
      returned: success
description:
  description:
    - Specifies the description of an ECS, which is a null string by default. Can contain a maximum of 85 characters. Cannot
      contain special characters, such as < and >.
  type: str
  returned: success
eip_id:
  description:
    - Specifies the ID of the elastic IP address assigned to the ECS. Only elastic IP addresses in the DOWN state can be assigned.
  type: str
  returned: success
enable_auto_recovery:
  description:
    - Specifies whether automatic recovery is enabled on the ECS.
  type: bool
  returned: success
enterprise_project_id:
  description:
    - Specifies the ID of the enterprise project to which the ECS belongs.
  type: str
  returned: success
security_groups:
  description:
    - Specifies the security groups of the ECS. If this parameter is left blank, the default security group is bound to the
      ECS by default.
  type: list
  returned: success
server_metadata:
  description:
    - Specifies the metadata of ECS to be created.
  type: dict
  returned: success
server_tags:
  description:
    - Specifies the tags of an ECS. When you create ECSs, one ECS supports up to 10 tags.
  type: dict
  returned: success
ssh_key_name:
  description:
    - Specifies the name of the SSH key used for logging in to the ECS.
  type: str
  returned: success
user_data:
  description:
    - Specifies the user data to be injected during the ECS creation process. Text, text files, and gzip files can be injected.
      The content to be injected must be encoded with base64. The maximum size of the content to be injected (before encoding)
      is 32 KB. For Linux ECSs, this parameter does not take effect when adminPass is used.
  type: str
  returned: success
config_drive:
  description:
    - Specifies the configuration driver.
  type: str
  returned: success
created:
  description:
    - Specifies the time when an ECS was created.
  type: str
  returned: success
disk_config_type:
  description:
    - Specifies the disk configuration type. MANUAL is The image space is not expanded. AUTO is the image space of the system
      disk is expanded to be as same as the flavor.
  type: str
  returned: success
host_name:
  description:
    - Specifies the host name of the ECS.
  type: str
  returned: success
image_name:
  description:
    - Specifies the image name of the ECS.
  type: str
  returned: success
power_state:
  description:
    - Specifies the power status of the ECS.
  type: int
  returned: success
server_alias:
  description:
    - Specifies the ECS alias.
  type: str
  returned: success
status:
  description:
    - Specifies the ECS status. Options are ACTIVE, REBOOT, HARD_REBOOT, REBUILD, MIGRATING, BUILD, SHUTOFF, RESIZE, VERIFY_RESIZE,
      ERROR, and DELETED.
  type: str
  returned: success
"""

from ansible_collections.community.general.plugins.module_utils.hwc_utils import (
    Config,
    HwcClientException,
    HwcModule,
    are_different_dicts,
    build_path,
    get_region,
    is_empty_value,
    navigate_value,
    wait_to_finish,
)


def build_module():
    return HwcModule(
        argument_spec=dict(
            state=dict(default="present", choices=["present", "absent"], type="str"),
            timeouts=dict(
                type="dict",
                options=dict(
                    create=dict(default="30m", type="str"),
                    update=dict(default="30m", type="str"),
                    delete=dict(default="30m", type="str"),
                ),
                default=dict(),
            ),
            availability_zone=dict(type="str", required=True),
            flavor_name=dict(type="str", required=True),
            image_id=dict(type="str", required=True),
            name=dict(type="str", required=True),
            nics=dict(
                type="list",
                required=True,
                elements="dict",
                options=dict(ip_address=dict(type="str", required=True), subnet_id=dict(type="str", required=True)),
            ),
            root_volume=dict(
                type="dict",
                required=True,
                options=dict(
                    volume_type=dict(type="str", required=True), size=dict(type="int"), snapshot_id=dict(type="str")
                ),
            ),
            vpc_id=dict(type="str", required=True),
            admin_pass=dict(type="str", no_log=True),
            data_volumes=dict(
                type="list",
                elements="dict",
                options=dict(volume_id=dict(type="str", required=True), device=dict(type="str")),
            ),
            description=dict(type="str"),
            eip_id=dict(type="str"),
            enable_auto_recovery=dict(type="bool"),
            enterprise_project_id=dict(type="str"),
            security_groups=dict(type="list", elements="str"),
            server_metadata=dict(type="dict"),
            server_tags=dict(type="dict"),
            ssh_key_name=dict(type="str"),
            user_data=dict(type="str"),
        ),
        supports_check_mode=True,
    )


def main():
    """Main function"""

    module = build_module()
    config = Config(module, "ecs")

    try:
        _init(config)
        is_exist = module.params["id"]

        result = None
        changed = False
        if module.params["state"] == "present":
            if not is_exist:
                if not module.check_mode:
                    create(config)
                changed = True

            inputv = user_input_parameters(module)
            resp, array_index = read_resource(config)
            result = build_state(inputv, resp, array_index)
            set_readonly_options(inputv, result)
            if are_different_dicts(inputv, result):
                if not module.check_mode:
                    update(config, inputv, result)

                    inputv = user_input_parameters(module)
                    resp, array_index = read_resource(config)
                    result = build_state(inputv, resp, array_index)
                    set_readonly_options(inputv, result)
                    if are_different_dicts(inputv, result):
                        raise Exception("Update resource failed, some attributes are not updated")

                changed = True

            result["id"] = module.params.get("id")
        else:
            result = dict()
            if is_exist:
                if not module.check_mode:
                    delete(config)
                changed = True

    except Exception as ex:
        module.fail_json(msg=str(ex))

    else:
        result["changed"] = changed
        module.exit_json(**result)


def _init(config):
    module = config.module
    if module.params["id"]:
        return

    v = search_resource(config)
    n = len(v)
    if n > 1:
        raise Exception(f"Found more than one resource({', '.join([navigate_value(i, ['id']) for i in v])})")

    if n == 1:
        module.params["id"] = navigate_value(v[0], ["id"])


def user_input_parameters(module):
    return {
        "admin_pass": module.params.get("admin_pass"),
        "availability_zone": module.params.get("availability_zone"),
        "data_volumes": module.params.get("data_volumes"),
        "description": module.params.get("description"),
        "eip_id": module.params.get("eip_id"),
        "enable_auto_recovery": module.params.get("enable_auto_recovery"),
        "enterprise_project_id": module.params.get("enterprise_project_id"),
        "flavor_name": module.params.get("flavor_name"),
        "image_id": module.params.get("image_id"),
        "name": module.params.get("name"),
        "nics": module.params.get("nics"),
        "root_volume": module.params.get("root_volume"),
        "security_groups": module.params.get("security_groups"),
        "server_metadata": module.params.get("server_metadata"),
        "server_tags": module.params.get("server_tags"),
        "ssh_key_name": module.params.get("ssh_key_name"),
        "user_data": module.params.get("user_data"),
        "vpc_id": module.params.get("vpc_id"),
    }


def create(config):
    module = config.module
    client = config.client(get_region(module), "ecs", "project")
    timeout = 60 * int(module.params["timeouts"]["create"].rstrip("m"))
    opts = user_input_parameters(module)
    opts["ansible_module"] = module

    params = build_create_parameters(opts)
    r = send_create_request(module, params, client)
    obj = async_wait(config, r, client, timeout)

    sub_job_identity = {
        "job_type": "createSingleServer",
    }
    for item in navigate_value(obj, ["entities", "sub_jobs"]):
        for k, v in sub_job_identity.items():
            if item[k] != v:
                break
        else:
            obj = item
            break
    else:
        raise Exception("Can't find the sub job")
    module.params["id"] = navigate_value(obj, ["entities", "server_id"])


def update(config, expect_state, current_state):
    module = config.module
    expect_state["current_state"] = current_state
    current_state["current_state"] = current_state
    timeout = 60 * int(module.params["timeouts"]["update"].rstrip("m"))
    client = config.client(get_region(module), "ecs", "project")

    params = build_delete_nics_parameters(expect_state)
    params1 = build_delete_nics_parameters(current_state)
    if params and are_different_dicts(params, params1):
        r = send_delete_nics_request(module, params, client)
        async_wait(config, r, client, timeout)

    params = build_set_auto_recovery_parameters(expect_state)
    params1 = build_set_auto_recovery_parameters(current_state)
    if params and are_different_dicts(params, params1):
        send_set_auto_recovery_request(module, params, client)

    params = build_attach_nics_parameters(expect_state)
    params1 = build_attach_nics_parameters(current_state)
    if params and are_different_dicts(params, params1):
        r = send_attach_nics_request(module, params, client)
        async_wait(config, r, client, timeout)

    multi_invoke_delete_volume(config, expect_state, client, timeout)

    multi_invoke_attach_data_disk(config, expect_state, client, timeout)


def delete(config):
    module = config.module
    client = config.client(get_region(module), "ecs", "project")
    timeout = 60 * int(module.params["timeouts"]["delete"].rstrip("m"))

    opts = user_input_parameters(module)
    opts["ansible_module"] = module

    params = build_delete_parameters(opts)
    if params:
        r = send_delete_request(module, params, client)
        async_wait(config, r, client, timeout)


def read_resource(config):
    module = config.module
    client = config.client(get_region(module), "ecs", "project")

    res = {}

    r = send_read_request(module, client)
    preprocess_read_response(r)
    res["read"] = fill_read_resp_body(r)

    r = send_read_auto_recovery_request(module, client)
    res["read_auto_recovery"] = fill_read_auto_recovery_resp_body(r)

    return res, None


def preprocess_read_response(resp):
    v = resp.get("os-extended-volumes:volumes_attached")
    if v and isinstance(v, list):
        for i in range(len(v)):
            if v[i].get("bootIndex") == "0":
                root_volume = v[i]

                if (i + 1) != len(v):
                    v[i] = v[-1]

                v.pop()

                resp["root_volume"] = root_volume
                break

    v = resp.get("addresses")
    if v:
        rv = {}
        eips = []
        for val in v.values():
            for item in val:
                if item["OS-EXT-IPS:type"] == "floating":
                    eips.append(item)
                else:
                    rv[item["OS-EXT-IPS:port_id"]] = item

        for item in eips:
            k = item["OS-EXT-IPS:port_id"]
            if k in rv:
                rv[k]["eip_address"] = item.get("addr", "")
            else:
                rv[k] = item
                item["eip_address"] = item.get("addr", "")
                item["addr"] = ""

        resp["address"] = rv.values()


def build_state(opts, response, array_index):
    states = flatten_options(response, array_index)
    set_unreadable_options(opts, states)
    adjust_options(opts, states)
    return states


def _build_query_link(opts):
    query_params = []

    v = navigate_value(opts, ["enterprise_project_id"])
    if v or v in [False, 0]:
        query_params.append(f"enterprise_project_id={str(v) if v else str(v).lower()}")

    v = navigate_value(opts, ["name"])
    if v or v in [False, 0]:
        query_params.append(f"name={str(v) if v else str(v).lower()}")

    query_link = "?limit=10&offset={offset}"
    if query_params:
        query_link += f"&{'&'.join(query_params)}"

    return query_link


def search_resource(config):
    module = config.module
    client = config.client(get_region(module), "ecs", "project")
    opts = user_input_parameters(module)
    identity_obj = _build_identity_object(opts)
    query_link = _build_query_link(opts)
    link = f"cloudservers/detail{query_link}"

    result = []
    p = {"offset": 1}
    while True:
        url = link.format(**p)
        r = send_list_request(module, client, url)
        if not r:
            break

        for item in r:
            item = fill_list_resp_body(item)
            adjust_list_resp(identity_obj, item)
            if not are_different_dicts(identity_obj, item):
                result.append(item)

        if len(result) > 1:
            break

        p["offset"] += 1

    return result


def build_delete_nics_parameters(opts):
    params = dict()

    v = expand_delete_nics_nics(opts, None)
    if not is_empty_value(v):
        params["nics"] = v

    return params


def expand_delete_nics_nics(d, array_index):
    cv = d["current_state"].get("nics")
    if not cv:
        return None

    val = cv

    ev = d.get("nics")
    if ev:
        m = [item.get("ip_address") for item in ev]
        val = [item for item in cv if item.get("ip_address") not in m]

    r = []
    for item in val:
        transformed = dict()

        v = item.get("port_id")
        if not is_empty_value(v):
            transformed["id"] = v

        if transformed:
            r.append(transformed)

    return r


def send_delete_nics_request(module, params, client):
    url = build_path(module, "cloudservers/{id}/nics/delete")

    try:
        r = client.post(url, params)
    except HwcClientException as ex:
        msg = f"module(hwc_ecs_instance): error running api(delete_nics), error: {ex}"
        module.fail_json(msg=msg)

    return r


def build_set_auto_recovery_parameters(opts):
    params = dict()

    v = expand_set_auto_recovery_support_auto_recovery(opts, None)
    if v is not None:
        params["support_auto_recovery"] = v

    return params


def expand_set_auto_recovery_support_auto_recovery(d, array_index):
    v = navigate_value(d, ["enable_auto_recovery"], None)
    return None if v is None else str(v).lower()


def send_set_auto_recovery_request(module, params, client):
    url = build_path(module, "cloudservers/{id}/autorecovery")

    try:
        r = client.put(url, params)
    except HwcClientException as ex:
        msg = f"module(hwc_ecs_instance): error running api(set_auto_recovery), error: {ex}"
        module.fail_json(msg=msg)

    return r


def build_create_parameters(opts):
    params = dict()

    v = navigate_value(opts, ["admin_pass"], None)
    if not is_empty_value(v):
        params["adminPass"] = v

    v = navigate_value(opts, ["availability_zone"], None)
    if not is_empty_value(v):
        params["availability_zone"] = v

    v = navigate_value(opts, ["description"], None)
    if not is_empty_value(v):
        params["description"] = v

    v = expand_create_extendparam(opts, None)
    if not is_empty_value(v):
        params["extendparam"] = v

    v = navigate_value(opts, ["flavor_name"], None)
    if not is_empty_value(v):
        params["flavorRef"] = v

    v = navigate_value(opts, ["image_id"], None)
    if not is_empty_value(v):
        params["imageRef"] = v

    v = navigate_value(opts, ["ssh_key_name"], None)
    if not is_empty_value(v):
        params["key_name"] = v

    v = navigate_value(opts, ["server_metadata"], None)
    if not is_empty_value(v):
        params["metadata"] = v

    v = navigate_value(opts, ["name"], None)
    if not is_empty_value(v):
        params["name"] = v

    v = expand_create_nics(opts, None)
    if not is_empty_value(v):
        params["nics"] = v

    v = expand_create_publicip(opts, None)
    if not is_empty_value(v):
        params["publicip"] = v

    v = expand_create_root_volume(opts, None)
    if not is_empty_value(v):
        params["root_volume"] = v

    v = expand_create_security_groups(opts, None)
    if not is_empty_value(v):
        params["security_groups"] = v

    v = expand_create_server_tags(opts, None)
    if not is_empty_value(v):
        params["server_tags"] = v

    v = navigate_value(opts, ["user_data"], None)
    if not is_empty_value(v):
        params["user_data"] = v

    v = navigate_value(opts, ["vpc_id"], None)
    if not is_empty_value(v):
        params["vpcid"] = v

    if not params:
        return params

    params = {"server": params}

    return params


def expand_create_extendparam(d, array_index):
    r = dict()

    r["chargingMode"] = 0

    v = navigate_value(d, ["enterprise_project_id"], array_index)
    if not is_empty_value(v):
        r["enterprise_project_id"] = v

    v = navigate_value(d, ["enable_auto_recovery"], array_index)
    if not is_empty_value(v):
        r["support_auto_recovery"] = v

    return r


def expand_create_nics(d, array_index):
    new_ai = dict()
    if array_index:
        new_ai.update(array_index)

    req = []

    v = navigate_value(d, ["nics"], new_ai)

    if not v:
        return req
    n = len(v)
    for i in range(n):
        new_ai["nics"] = i
        transformed = dict()

        v = navigate_value(d, ["nics", "ip_address"], new_ai)
        if not is_empty_value(v):
            transformed["ip_address"] = v

        v = navigate_value(d, ["nics", "subnet_id"], new_ai)
        if not is_empty_value(v):
            transformed["subnet_id"] = v

        if transformed:
            req.append(transformed)

    return req


def expand_create_publicip(d, array_index):
    r = dict()

    v = navigate_value(d, ["eip_id"], array_index)
    if not is_empty_value(v):
        r["id"] = v

    return r


def expand_create_root_volume(d, array_index):
    r = dict()

    v = expand_create_root_volume_extendparam(d, array_index)
    if not is_empty_value(v):
        r["extendparam"] = v

    v = navigate_value(d, ["root_volume", "size"], array_index)
    if not is_empty_value(v):
        r["size"] = v

    v = navigate_value(d, ["root_volume", "volume_type"], array_index)
    if not is_empty_value(v):
        r["volumetype"] = v

    return r


def expand_create_root_volume_extendparam(d, array_index):
    r = dict()

    v = navigate_value(d, ["root_volume", "snapshot_id"], array_index)
    if not is_empty_value(v):
        r["snapshotId"] = v

    return r


def expand_create_security_groups(d, array_index):
    v = d.get("security_groups")
    if not v:
        return None

    return [{"id": i} for i in v]


def expand_create_server_tags(d, array_index):
    v = d.get("server_tags")
    if not v:
        return None

    return [{"key": k, "value": v1} for k, v1 in v.items()]


def send_create_request(module, params, client):
    url = "cloudservers"
    try:
        r = client.post(url, params)
    except HwcClientException as ex:
        msg = f"module(hwc_ecs_instance): error running api(create), error: {ex}"
        module.fail_json(msg=msg)

    return r


def build_attach_nics_parameters(opts):
    params = dict()

    v = expand_attach_nics_nics(opts, None)
    if not is_empty_value(v):
        params["nics"] = v

    return params


def expand_attach_nics_nics(d, array_index):
    ev = d.get("nics")
    if not ev:
        return None

    val = ev

    cv = d["current_state"].get("nics")
    if cv:
        m = [item.get("ip_address") for item in cv]
        val = [item for item in ev if item.get("ip_address") not in m]

    r = []
    for item in val:
        transformed = dict()

        v = item.get("ip_address")
        if not is_empty_value(v):
            transformed["ip_address"] = v

        v = item.get("subnet_id")
        if not is_empty_value(v):
            transformed["subnet_id"] = v

        if transformed:
            r.append(transformed)

    return r


def send_attach_nics_request(module, params, client):
    url = build_path(module, "cloudservers/{id}/nics")

    try:
        r = client.post(url, params)
    except HwcClientException as ex:
        msg = f"module(hwc_ecs_instance): error running api(attach_nics), error: {ex}"
        module.fail_json(msg=msg)

    return r


def send_delete_volume_request(module, params, client, info):
    path_parameters = {
        "volume_id": ["volume_id"],
    }
    data = {key: navigate_value(info, path) for key, path in path_parameters.items()}

    url = build_path(module, "cloudservers/{id}/detachvolume/{volume_id}", data)

    try:
        r = client.delete(url, params)
    except HwcClientException as ex:
        msg = f"module(hwc_ecs_instance): error running api(delete_volume), error: {ex}"
        module.fail_json(msg=msg)

    return r


def build_attach_data_disk_parameters(opts, array_index):
    params = dict()

    v = expand_attach_data_disk_volume_attachment(opts, array_index)
    if not is_empty_value(v):
        params["volumeAttachment"] = v

    return params


def expand_attach_data_disk_volume_attachment(d, array_index):
    r = dict()

    v = navigate_value(d, ["data_volumes", "device"], array_index)
    if not is_empty_value(v):
        r["device"] = v

    v = navigate_value(d, ["data_volumes", "volume_id"], array_index)
    if not is_empty_value(v):
        r["volumeId"] = v

    return r


def send_attach_data_disk_request(module, params, client):
    url = build_path(module, "cloudservers/{id}/attachvolume")

    try:
        r = client.post(url, params)
    except HwcClientException as ex:
        msg = f"module(hwc_ecs_instance): error running api(attach_data_disk), error: {ex}"
        module.fail_json(msg=msg)

    return r


def build_delete_parameters(opts):
    params = dict()

    params["delete_publicip"] = False

    params["delete_volume"] = False

    v = expand_delete_servers(opts, None)
    if not is_empty_value(v):
        params["servers"] = v

    return params


def expand_delete_servers(d, array_index):
    new_ai = dict()
    if array_index:
        new_ai.update(array_index)

    req = []

    transformed = dict()

    v = expand_delete_servers_id(d, new_ai)
    if not is_empty_value(v):
        transformed["id"] = v

    if transformed:
        req.append(transformed)

    return req


def expand_delete_servers_id(d, array_index):
    return d["ansible_module"].params.get("id")


def send_delete_request(module, params, client):
    url = "cloudservers/delete"
    try:
        r = client.post(url, params)
    except HwcClientException as ex:
        msg = f"module(hwc_ecs_instance): error running api(delete), error: {ex}"
        module.fail_json(msg=msg)

    return r


def async_wait(config, result, client, timeout):
    module = config.module

    url = build_path(module, "jobs/{job_id}", result)

    def _query_status():
        r = None
        try:
            r = client.get(url, timeout=timeout)
        except HwcClientException:
            return None, ""

        try:
            s = navigate_value(r, ["status"])
            return r, s
        except Exception:
            return None, ""

    try:
        return wait_to_finish(["SUCCESS"], ["RUNNING", "INIT"], _query_status, timeout)
    except Exception as ex:
        module.fail_json(msg=f"module(hwc_ecs_instance): error waiting to be done, error= {ex}")


def multi_invoke_delete_volume(config, opts, client, timeout):
    module = config.module

    opts1 = None
    expect = opts["data_volumes"]
    current = opts["current_state"]["data_volumes"]
    if expect and current:
        v = [i["volume_id"] for i in expect]
        opts1 = {"data_volumes": [i for i in current if i["volume_id"] not in v]}

    loop_val = navigate_value(opts1, ["data_volumes"])
    if not loop_val:
        return

    for i in range(len(loop_val)):
        r = send_delete_volume_request(module, None, client, loop_val[i])
        async_wait(config, r, client, timeout)


def multi_invoke_attach_data_disk(config, opts, client, timeout):
    module = config.module

    opts1 = opts
    expect = opts["data_volumes"]
    current = opts["current_state"]["data_volumes"]
    if expect and current:
        v = [i["volume_id"] for i in current]
        opts1 = {"data_volumes": [i for i in expect if i["volume_id"] not in v]}

    loop_val = navigate_value(opts1, ["data_volumes"])
    if not loop_val:
        return

    for i in range(len(loop_val)):
        params = build_attach_data_disk_parameters(opts1, {"data_volumes": i})
        r = send_attach_data_disk_request(module, params, client)
        async_wait(config, r, client, timeout)


def send_read_request(module, client):
    url = build_path(module, "cloudservers/{id}")

    r = None
    try:
        r = client.get(url)
    except HwcClientException as ex:
        msg = f"module(hwc_ecs_instance): error running api(read), error: {ex}"
        module.fail_json(msg=msg)

    return navigate_value(r, ["server"], None)


def fill_read_resp_body(body):
    result = dict()

    result["OS-DCF:diskConfig"] = body.get("OS-DCF:diskConfig")

    result["OS-EXT-AZ:availability_zone"] = body.get("OS-EXT-AZ:availability_zone")

    result["OS-EXT-SRV-ATTR:hostname"] = body.get("OS-EXT-SRV-ATTR:hostname")

    result["OS-EXT-SRV-ATTR:instance_name"] = body.get("OS-EXT-SRV-ATTR:instance_name")

    result["OS-EXT-SRV-ATTR:user_data"] = body.get("OS-EXT-SRV-ATTR:user_data")

    result["OS-EXT-STS:power_state"] = body.get("OS-EXT-STS:power_state")

    v = fill_read_resp_address(body.get("address"))
    result["address"] = v

    result["config_drive"] = body.get("config_drive")

    result["created"] = body.get("created")

    result["description"] = body.get("description")

    result["enterprise_project_id"] = body.get("enterprise_project_id")

    v = fill_read_resp_flavor(body.get("flavor"))
    result["flavor"] = v

    result["id"] = body.get("id")

    v = fill_read_resp_image(body.get("image"))
    result["image"] = v

    result["key_name"] = body.get("key_name")

    v = fill_read_resp_metadata(body.get("metadata"))
    result["metadata"] = v

    result["name"] = body.get("name")

    v = fill_read_resp_os_extended_volumes_volumes_attached(body.get("os-extended-volumes:volumes_attached"))
    result["os-extended-volumes:volumes_attached"] = v

    v = fill_read_resp_root_volume(body.get("root_volume"))
    result["root_volume"] = v

    result["status"] = body.get("status")

    result["tags"] = body.get("tags")

    return result


def fill_read_resp_address(value):
    if not value:
        return None

    result = []
    for item in value:
        val = dict()

        val["OS-EXT-IPS:port_id"] = item.get("OS-EXT-IPS:port_id")

        val["OS-EXT-IPS:type"] = item.get("OS-EXT-IPS:type")

        val["addr"] = item.get("addr")

        result.append(val)

    return result


def fill_read_resp_flavor(value):
    if not value:
        return None

    result = dict()

    result["id"] = value.get("id")

    return result


def fill_read_resp_image(value):
    if not value:
        return None

    result = dict()

    result["id"] = value.get("id")

    return result


def fill_read_resp_metadata(value):
    if not value:
        return None

    result = dict()

    result["image_name"] = value.get("image_name")

    result["vpc_id"] = value.get("vpc_id")

    return result


def fill_read_resp_os_extended_volumes_volumes_attached(value):
    if not value:
        return None

    result = []
    for item in value:
        val = dict()

        val["bootIndex"] = item.get("bootIndex")

        val["device"] = item.get("device")

        val["id"] = item.get("id")

        result.append(val)

    return result


def fill_read_resp_root_volume(value):
    if not value:
        return None

    result = dict()

    result["device"] = value.get("device")

    result["id"] = value.get("id")

    return result


def send_read_auto_recovery_request(module, client):
    url = build_path(module, "cloudservers/{id}/autorecovery")

    r = None
    try:
        r = client.get(url)
    except HwcClientException as ex:
        msg = f"module(hwc_ecs_instance): error running api(read_auto_recovery), error: {ex}"
        module.fail_json(msg=msg)

    return r


def fill_read_auto_recovery_resp_body(body):
    result = dict()

    result["support_auto_recovery"] = body.get("support_auto_recovery")

    return result


def flatten_options(response, array_index):
    r = dict()

    v = navigate_value(response, ["read", "OS-EXT-AZ:availability_zone"], array_index)
    r["availability_zone"] = v

    v = navigate_value(response, ["read", "config_drive"], array_index)
    r["config_drive"] = v

    v = navigate_value(response, ["read", "created"], array_index)
    r["created"] = v

    v = flatten_data_volumes(response, array_index)
    r["data_volumes"] = v

    v = navigate_value(response, ["read", "description"], array_index)
    r["description"] = v

    v = navigate_value(response, ["read", "OS-DCF:diskConfig"], array_index)
    r["disk_config_type"] = v

    v = flatten_enable_auto_recovery(response, array_index)
    r["enable_auto_recovery"] = v

    v = navigate_value(response, ["read", "enterprise_project_id"], array_index)
    r["enterprise_project_id"] = v

    v = navigate_value(response, ["read", "flavor", "id"], array_index)
    r["flavor_name"] = v

    v = navigate_value(response, ["read", "OS-EXT-SRV-ATTR:hostname"], array_index)
    r["host_name"] = v

    v = navigate_value(response, ["read", "image", "id"], array_index)
    r["image_id"] = v

    v = navigate_value(response, ["read", "metadata", "image_name"], array_index)
    r["image_name"] = v

    v = navigate_value(response, ["read", "name"], array_index)
    r["name"] = v

    v = flatten_nics(response, array_index)
    r["nics"] = v

    v = navigate_value(response, ["read", "OS-EXT-STS:power_state"], array_index)
    r["power_state"] = v

    v = flatten_root_volume(response, array_index)
    r["root_volume"] = v

    v = navigate_value(response, ["read", "OS-EXT-SRV-ATTR:instance_name"], array_index)
    r["server_alias"] = v

    v = flatten_server_tags(response, array_index)
    r["server_tags"] = v

    v = navigate_value(response, ["read", "key_name"], array_index)
    r["ssh_key_name"] = v

    v = navigate_value(response, ["read", "status"], array_index)
    r["status"] = v

    v = navigate_value(response, ["read", "OS-EXT-SRV-ATTR:user_data"], array_index)
    r["user_data"] = v

    v = navigate_value(response, ["read", "metadata", "vpc_id"], array_index)
    r["vpc_id"] = v

    return r


def flatten_data_volumes(d, array_index):
    v = navigate_value(d, ["read", "os-extended-volumes:volumes_attached"], array_index)
    if not v:
        return None
    n = len(v)
    result = []

    new_ai = dict()
    if array_index:
        new_ai.update(array_index)

    for i in range(n):
        new_ai["read.os-extended-volumes:volumes_attached"] = i

        val = dict()

        v = navigate_value(d, ["read", "os-extended-volumes:volumes_attached", "device"], new_ai)
        val["device"] = v

        v = navigate_value(d, ["read", "os-extended-volumes:volumes_attached", "id"], new_ai)
        val["volume_id"] = v

        for v in val.values():
            if v is not None:
                result.append(val)
                break

    return result if result else None


def flatten_enable_auto_recovery(d, array_index):
    v = navigate_value(d, ["read_auto_recovery", "support_auto_recovery"], array_index)
    return v == "true"


def flatten_nics(d, array_index):
    v = navigate_value(d, ["read", "address"], array_index)
    if not v:
        return None
    n = len(v)
    result = []

    new_ai = dict()
    if array_index:
        new_ai.update(array_index)

    for i in range(n):
        new_ai["read.address"] = i

        val = dict()

        v = navigate_value(d, ["read", "address", "addr"], new_ai)
        val["ip_address"] = v

        v = navigate_value(d, ["read", "address", "OS-EXT-IPS:port_id"], new_ai)
        val["port_id"] = v

        for v in val.values():
            if v is not None:
                result.append(val)
                break

    return result if result else None


def flatten_root_volume(d, array_index):
    result = dict()

    v = navigate_value(d, ["read", "root_volume", "device"], array_index)
    result["device"] = v

    v = navigate_value(d, ["read", "root_volume", "id"], array_index)
    result["volume_id"] = v

    for v in result.values():
        if v is not None:
            return result
    return None


def flatten_server_tags(d, array_index):
    v = navigate_value(d, ["read", "tags"], array_index)
    if not v:
        return None

    r = dict()
    for item in v:
        v1 = item.split("=")
        if v1:
            r[v1[0]] = v1[1]
    return r


def adjust_options(opts, states):
    adjust_data_volumes(opts, states)

    adjust_nics(opts, states)


def adjust_data_volumes(parent_input, parent_cur):
    iv = parent_input.get("data_volumes")
    if not (iv and isinstance(iv, list)):
        return

    cv = parent_cur.get("data_volumes")
    if not (cv and isinstance(cv, list)):
        return

    lcv = len(cv)
    result = []
    q = []
    for iiv in iv:
        if len(q) == lcv:
            break

        icv = None
        for j in range(lcv):
            if j in q:
                continue

            icv = cv[j]

            if iiv["volume_id"] != icv["volume_id"]:
                continue

            result.append(icv)
            q.append(j)
            break
        else:
            break

    if len(q) != lcv:
        for i in range(lcv):
            if i not in q:
                result.append(cv[i])

    if len(result) != lcv:
        raise Exception("adjust property(data_volumes) failed, the array number is not equal")

    parent_cur["data_volumes"] = result


def adjust_nics(parent_input, parent_cur):
    iv = parent_input.get("nics")
    if not (iv and isinstance(iv, list)):
        return

    cv = parent_cur.get("nics")
    if not (cv and isinstance(cv, list)):
        return

    lcv = len(cv)
    result = []
    q = []
    for iiv in iv:
        if len(q) == lcv:
            break

        icv = None
        for j in range(lcv):
            if j in q:
                continue

            icv = cv[j]

            if iiv["ip_address"] != icv["ip_address"]:
                continue

            result.append(icv)
            q.append(j)
            break
        else:
            break

    if len(q) != lcv:
        for i in range(lcv):
            if i not in q:
                result.append(cv[i])

    if len(result) != lcv:
        raise Exception("adjust property(nics) failed, the array number is not equal")

    parent_cur["nics"] = result


def set_unreadable_options(opts, states):
    states["admin_pass"] = opts.get("admin_pass")

    states["eip_id"] = opts.get("eip_id")

    set_unread_nics(opts.get("nics"), states.get("nics"))

    set_unread_root_volume(opts.get("root_volume"), states.get("root_volume"))

    states["security_groups"] = opts.get("security_groups")

    states["server_metadata"] = opts.get("server_metadata")


def set_unread_nics(inputv, curv):
    if not (inputv and isinstance(inputv, list)):
        return

    if not (curv and isinstance(curv, list)):
        return

    lcv = len(curv)
    q = []
    for iv in inputv:
        if len(q) == lcv:
            break

        cv = None
        for j in range(lcv):
            if j in q:
                continue

            cv = curv[j]

            if iv["ip_address"] != cv["ip_address"]:
                continue

            q.append(j)
            break
        else:
            continue

        cv["subnet_id"] = iv.get("subnet_id")


def set_unread_root_volume(inputv, curv):
    if not (inputv and isinstance(inputv, dict)):
        return

    if not (curv and isinstance(curv, dict)):
        return

    curv["size"] = inputv.get("size")

    curv["snapshot_id"] = inputv.get("snapshot_id")

    curv["volume_type"] = inputv.get("volume_type")


def set_readonly_options(opts, states):
    opts["config_drive"] = states.get("config_drive")

    opts["created"] = states.get("created")

    opts["disk_config_type"] = states.get("disk_config_type")

    opts["host_name"] = states.get("host_name")

    opts["image_name"] = states.get("image_name")

    set_readonly_nics(opts.get("nics"), states.get("nics"))

    opts["power_state"] = states.get("power_state")

    set_readonly_root_volume(opts.get("root_volume"), states.get("root_volume"))

    opts["server_alias"] = states.get("server_alias")

    opts["status"] = states.get("status")


def set_readonly_nics(inputv, curv):
    if not (curv and isinstance(curv, list)):
        return

    if not (inputv and isinstance(inputv, list)):
        return

    lcv = len(curv)
    q = []
    for iv in inputv:
        if len(q) == lcv:
            break

        cv = None
        for j in range(lcv):
            if j in q:
                continue

            cv = curv[j]

            if iv["ip_address"] != cv["ip_address"]:
                continue

            q.append(j)
            break
        else:
            continue

        iv["port_id"] = cv.get("port_id")


def set_readonly_root_volume(inputv, curv):
    if not (inputv and isinstance(inputv, dict)):
        return

    if not (curv and isinstance(curv, dict)):
        return

    inputv["device"] = curv.get("device")

    inputv["volume_id"] = curv.get("volume_id")


def send_list_request(module, client, url):
    r = None
    try:
        r = client.get(url)
    except HwcClientException as ex:
        msg = f"module(hwc_ecs_instance): error running api(list), error: {ex}"
        module.fail_json(msg=msg)

    return navigate_value(r, ["servers"], None)


def _build_identity_object(all_opts):
    result = dict()

    result["OS-DCF:diskConfig"] = None

    v = navigate_value(all_opts, ["availability_zone"], None)
    result["OS-EXT-AZ:availability_zone"] = v

    result["OS-EXT-SRV-ATTR:hostname"] = None

    result["OS-EXT-SRV-ATTR:instance_name"] = None

    v = navigate_value(all_opts, ["user_data"], None)
    result["OS-EXT-SRV-ATTR:user_data"] = v

    result["OS-EXT-STS:power_state"] = None

    result["config_drive"] = None

    result["created"] = None

    v = navigate_value(all_opts, ["description"], None)
    result["description"] = v

    v = navigate_value(all_opts, ["enterprise_project_id"], None)
    result["enterprise_project_id"] = v

    v = expand_list_flavor(all_opts, None)
    result["flavor"] = v

    result["id"] = None

    v = expand_list_image(all_opts, None)
    result["image"] = v

    v = navigate_value(all_opts, ["ssh_key_name"], None)
    result["key_name"] = v

    v = expand_list_metadata(all_opts, None)
    result["metadata"] = v

    v = navigate_value(all_opts, ["name"], None)
    result["name"] = v

    result["status"] = None

    v = expand_list_tags(all_opts, None)
    result["tags"] = v

    return result


def expand_list_flavor(d, array_index):
    r = dict()

    v = navigate_value(d, ["flavor_name"], array_index)
    r["id"] = v

    for v in r.values():
        if v is not None:
            return r
    return None


def expand_list_image(d, array_index):
    r = dict()

    v = navigate_value(d, ["image_id"], array_index)
    r["id"] = v

    for v in r.values():
        if v is not None:
            return r
    return None


def expand_list_metadata(d, array_index):
    r = dict()

    v = navigate_value(d, ["vpc_id"], array_index)
    r["vpc_id"] = v

    for v in r.values():
        if v is not None:
            return r
    return None


def expand_list_tags(d, array_index):
    v = d.get("server_tags")
    if not v:
        return None

    return [f"{k}={v1}" for k, v1 in v.items()]


def fill_list_resp_body(body):
    result = dict()

    result["OS-DCF:diskConfig"] = body.get("OS-DCF:diskConfig")

    result["OS-EXT-AZ:availability_zone"] = body.get("OS-EXT-AZ:availability_zone")

    result["OS-EXT-SRV-ATTR:hostname"] = body.get("OS-EXT-SRV-ATTR:hostname")

    result["OS-EXT-SRV-ATTR:instance_name"] = body.get("OS-EXT-SRV-ATTR:instance_name")

    result["OS-EXT-SRV-ATTR:user_data"] = body.get("OS-EXT-SRV-ATTR:user_data")

    result["OS-EXT-STS:power_state"] = body.get("OS-EXT-STS:power_state")

    result["config_drive"] = body.get("config_drive")

    result["created"] = body.get("created")

    result["description"] = body.get("description")

    result["enterprise_project_id"] = body.get("enterprise_project_id")

    v = fill_list_resp_flavor(body.get("flavor"))
    result["flavor"] = v

    result["id"] = body.get("id")

    v = fill_list_resp_image(body.get("image"))
    result["image"] = v

    result["key_name"] = body.get("key_name")

    v = fill_list_resp_metadata(body.get("metadata"))
    result["metadata"] = v

    result["name"] = body.get("name")

    result["status"] = body.get("status")

    result["tags"] = body.get("tags")

    return result


def fill_list_resp_flavor(value):
    if not value:
        return None

    result = dict()

    result["id"] = value.get("id")

    return result


def fill_list_resp_image(value):
    if not value:
        return None

    result = dict()

    result["id"] = value.get("id")

    return result


def fill_list_resp_metadata(value):
    if not value:
        return None

    result = dict()

    result["vpc_id"] = value.get("vpc_id")

    return result


def adjust_list_resp(opts, resp):
    adjust_list_api_tags(opts, resp)


def adjust_list_api_tags(parent_input, parent_cur):
    iv = parent_input.get("tags")
    if not (iv and isinstance(iv, list)):
        return

    cv = parent_cur.get("tags")
    if not (cv and isinstance(cv, list)):
        return

    result = []
    for iiv in iv:
        if iiv not in cv:
            break

        result.append(iiv)

        j = cv.index(iiv)
        cv[j] = cv[-1]
        cv.pop()

    if cv:
        result.extend(cv)
    parent_cur["tags"] = result


if __name__ == "__main__":
    main()
